package org.mina;

import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.filter.logging.MdcInjectionFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Chat {

	/** Choose your favorite port number. */
	private static final int PORT = 1234;

	/** Set this to true if you want to make the server SSL */
	private static final boolean USE_SSL = false;

	@Test
	public void ServerStart() throws Exception {

		NioSocketAcceptor acceptor = new NioSocketAcceptor();
		DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();

		MdcInjectionFilter mdcInjectionFilter = new MdcInjectionFilter();
		chain.addLast("mdc", mdcInjectionFilter);

		// Add SSL filter if SSL is enabled.
		if (USE_SSL) {
			 //addSSLSupport(chain);
		}

		chain.addLast("codec", new ProtocolCodecFilter(
				new TextLineCodecFactory()));

		addLogger(chain);

		// Bind
		acceptor.setHandler(new ChatProtocolHandler());
		acceptor.bind(new InetSocketAddress(PORT));

		System.out.println("Listening on port " + PORT);

	}

	private static void addLogger(DefaultIoFilterChainBuilder chain)
			throws Exception {
		chain.addLast("logger", new LoggingFilter());
		System.out.println("Logging ON");
	}

}

class ChatProtocolHandler extends IoHandlerAdapter {

	private final static Logger LOGGER = LoggerFactory
			.getLogger(ChatProtocolHandler.class);

	private final Set<IoSession> sessions = Collections
			.synchronizedSet(new HashSet<IoSession>());

	private final Set<String> users = Collections
			.synchronizedSet(new HashSet<String>());

	@Override
	public void exceptionCaught(IoSession session, Throwable cause) {
		LOGGER.warn("Unexpected exception.", cause);
		// Close connection when unexpected exception is caught.
		session.close(true);
	}

	@Override
	public void messageReceived(IoSession session, Object message) {
		Logger log = LoggerFactory.getLogger(ChatProtocolHandler.class);
		log.info("received: " + message);
		String theMessage = (String) message;
		String[] result = theMessage.split(" ", 2);
		String theCommand = result[0];

		try {

			ChatCommand command = ChatCommand.valueOf(theCommand);
			String user = (String) session.getAttribute("user");

			switch (command.toInt()) {

			case ChatCommand.QUIT:
				session.write("QUIT OK");
				session.close(true);
				break;
			case ChatCommand.LOGIN:

				if (user != null) {
					session.write("LOGIN ERROR user " + user
							+ " already logged in.");
					return;
				}

				if (result.length == 2) {
					user = result[1];
				} else {
					session.write("LOGIN ERROR invalid login command.");
					return;
				}

				// check if the username is already used
				if (users.contains(user)) {
					session.write("LOGIN ERROR the name " + user
							+ " is already used.");
					return;
				}

				sessions.add(session);
				session.setAttribute("user", user);
				MdcInjectionFilter.setProperty(session, "user", user);

				// Allow all users
				users.add(user);
				session.write("LOGIN OK");
				broadcast("The user " + user + " has joined the chat session.");
				break;

			case ChatCommand.BROADCAST:

				if (result.length == 2) {
					broadcast(user + ": " + result[1]);
				}
				break;
			default:
				LOGGER.info("Unhandled command: " + command);
				break;
			}

		} catch (IllegalArgumentException e) {
			LOGGER.debug("Illegal argument", e);
		}
	}

	public void broadcast(String message) {
		synchronized (sessions) {
			for (IoSession session : sessions) {
				if (session.isConnected()) {
					session.write("BROADCAST OK " + message);
				}
			}
		}
	}

	@Override
	public void sessionClosed(IoSession session) throws Exception {
		String user = (String) session.getAttribute("user");
		users.remove(user);
		sessions.remove(session);
		broadcast("The user " + user + " has left the chat session.");
	}

	public boolean isChatUser(String name) {
		return users.contains(name);
	}

	public int getNumberOfUsers() {
		return users.size();
	}

	public void kick(String name) {
		synchronized (sessions) {
			for (IoSession session : sessions) {
				if (name.equals(session.getAttribute("user"))) {
					session.close(true);
					break;
				}
			}
		}
	}

}
